/*
 *  Copyright 2019-2020 Zheng Jie
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.github.x19990416.mxpaas.tool.generator.utils;

import com.github.x19990416.mxpaas.tool.generator.domain.ColumnInfo;
import com.github.x19990416.mxpaas.tool.generator.domain.GenConfig;
import com.github.x19990416.tools.constant.Template;
import com.github.x19990416.tools.constant.TemplateConfig;
import com.github.x19990416.tools.constant.TemplateEngine;
import com.github.x19990416.tools.constant.engine.TemplateFactory;
import com.github.x19990416.tools.constant.engine.freemarker.FreemarkerEngine;
import com.google.common.base.CaseFormat;
import com.google.common.base.Charsets;
import com.google.common.base.Converter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.ObjectUtils;

import java.io.File;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 代码生成
 *
 * @author Zheng Jie
 * @date 2019-01-02
 */
@Slf4j
@SuppressWarnings({"unchecked", "all"})
public class GenUtil {

  private static final String TIMESTAMP = "Timestamp";

  private static final String BIGDECIMAL = "BigDecimal";

  public static final String PK = "PRI";

  public static final String EXTRA = "auto_increment";
  // 驼峰首字母大写
  private static final Converter<String, String> UPPER_CAMEL_FORMAT =
      CaseFormat.UPPER_UNDERSCORE.converterTo(CaseFormat.UPPER_CAMEL);
  // 驼峰首字母小写
  private static final Converter<String, String> LOWER_CAMEL_FORMAT =
      CaseFormat.UPPER_UNDERSCORE.converterTo(CaseFormat.LOWER_CAMEL);

  /**
   * 获取后端代码模板名称
   *
   * @return List
   */
  private static List<String> getAdminTemplateNames() {
    List<String> templateNames = new ArrayList<>();
    templateNames.add("Entity");
    templateNames.add("Dto");
    templateNames.add("Mapper");
    templateNames.add("Controller");
    templateNames.add("QueryCriteria");
    templateNames.add("Service");
    templateNames.add("ServiceImpl");
    templateNames.add("Repository");
    return templateNames;
  }

  /**
   * 获取前端代码模板名称
   *
   * @return List
   */
  private static List<String> getFrontTemplateNames() {
    List<String> templateNames = new ArrayList<>();
    templateNames.add("index");
    templateNames.add("api");
    return templateNames;
  }

  public static List<Map<String, Object>> preview(List<ColumnInfo> columns, GenConfig genConfig)
      throws Exception {
    Map<String, Object> genMap = getGenMap(columns, genConfig);
    List<Map<String, Object>> genList = new ArrayList<>();
    // 获取后端模版
    List<String> templates = getAdminTemplateNames();
    TemplateEngine engine =
        TemplateFactory.getTemplateEngine(FreemarkerEngine.class)
            .init(
                new TemplateConfig(
                    "template", Charsets.UTF_8, TemplateConfig.ResourceMode.CLASSPATH));
    for (String templateName : templates) {
      Map<String, Object> map = new HashMap<>(1);
      Template template =
          engine.getInstance("generator/admin/" + templateName + ".ftl");
      map.put("content", template.render(genMap));
      map.put("name", templateName);
      genList.add(map);
    }
    // 获取前端模版
    templates = getFrontTemplateNames();
    for (String templateName : templates) {
      Map<String, Object> map = new HashMap<>(1);
      Template template =
          engine.getInstance("generator/front/" + templateName + ".ftl");
      map.put(templateName, template.render(genMap));
      map.put("content", template.render(genMap));
      map.put("name", templateName);
      genList.add(map);
    }
    return genList;
  }

  public static String download(List<ColumnInfo> columns, GenConfig genConfig) throws Exception {
    // 拼接的路径：/tmpeladmin-gen-temp/，这个路径在Linux下需要root用户才有权限创建,非root用户会权限错误而失败，更改为：
    // /tmp/eladmin-gen-temp/
    // String tempPath =SYS_TEM_DIR + "eladmin-gen-temp" + File.separator + genConfig.getTableName()
    // + File.separator;
    String tempPath =
        System.getProperty("java.io.tmpdir")
            + File.separator
            + "eladmin-gen-temp"
            + File.separator
            + genConfig.getTableName()
            + File.separator;
    Map<String, Object> genMap = getGenMap(columns, genConfig);
    TemplateEngine engine =
        TemplateFactory.getTemplateEngine(FreemarkerEngine.class)
            .init(
                new TemplateConfig(
                    "template", Charsets.UTF_8, TemplateConfig.ResourceMode.CLASSPATH));

    // 生成后端代码
    List<String> templates = getAdminTemplateNames();
    for (String templateName : templates) {
      Template template =
          engine.getInstance("generator/admin/" + templateName + ".ftl");
      String filePath =
          getAdminFilePath(
              templateName,
              genConfig,
              genMap.get("className").toString(),
              tempPath + "eladmin" + File.separator);
      assert filePath != null;
      File file = new File(filePath);
      // 如果非覆盖生成
      if (!genConfig.getCover() && file.exists()) {
        continue;
      }
      // 生成代码
      genFile(file, template, genMap);
    }
    // 生成前端代码
    templates = getFrontTemplateNames();
    for (String templateName : templates) {
      Template template =
          engine.getInstance("generator/front/" + templateName + ".ftl");
      String path = tempPath + "eladmin-web" + File.separator;
      String apiPath = path + "src" + File.separator + "api" + File.separator;
      String srcPath =
          path
              + "src"
              + File.separator
              + "views"
              + File.separator
              + genMap.get("changeClassName").toString()
              + File.separator;
      String filePath =
          getFrontFilePath(
              templateName, apiPath, srcPath, genMap.get("changeClassName").toString());
      assert filePath != null;
      File file = new File(filePath);
      // 如果非覆盖生成
      if (!genConfig.getCover() && file.exists()) {
        continue;
      }
      // 生成代码
      genFile(file, template, genMap);
    }
    return tempPath;
  }

  public static void generatorCode(List<ColumnInfo> columnInfos, GenConfig genConfig)
      throws Exception {
    Map<String, Object> genMap = getGenMap(columnInfos, genConfig);
    TemplateEngine engine =
        TemplateFactory.getTemplateEngine(FreemarkerEngine.class)
            .init(
                new TemplateConfig(
                    "template", Charsets.UTF_8, TemplateConfig.ResourceMode.CLASSPATH));
    // 生成后端代码
    List<String> templates = getAdminTemplateNames();
    for (String templateName : templates) {
      Template template =
          engine.getInstance("generator/admin/" + templateName + ".ftl");
      String rootPath = System.getProperty("user.dir");
      String filePath =
          getAdminFilePath(templateName, genConfig, genMap.get("className").toString(), rootPath);

      assert filePath != null;
      File file = new File(filePath);

      // 如果非覆盖生成
      if (!genConfig.getCover() && file.exists()) {
        continue;
      }
      // 生成代码
      genFile(file, template, genMap);
    }

    // 生成前端代码
    templates = getFrontTemplateNames();
    for (String templateName : templates) {
      Template template =
          engine.getInstance("generator/front/" + templateName + ".ftl");
      String filePath =
          getFrontFilePath(
              templateName,
              genConfig.getApiPath(),
              genConfig.getPath(),
              genMap.get("changeClassName").toString());

      assert filePath != null;
      File file = new File(filePath);

      // 如果非覆盖生成
      if (!genConfig.getCover() && file.exists()) {
        continue;
      }
      // 生成代码
      genFile(file, template, genMap);
    }
  }

  // 获取模版数据
  private static Map<String, Object> getGenMap(List<ColumnInfo> columnInfos, GenConfig genConfig) {
    // 存储模版字段数据
    Map<String, Object> genMap = new HashMap<>(16);
    // 接口别名
    genMap.put("apiAlias", genConfig.getApiAlias());
    // 包名称
    genMap.put("package", genConfig.getPack());
    // 模块名称
    genMap.put("moduleName", genConfig.getModuleName());
    // 作者
    genMap.put("author", genConfig.getAuthor());
    // 创建日期
    genMap.put("date", LocalDate.now().toString());
    // 表名
    genMap.put("tableName", genConfig.getTableName());
    // 大写开头的类名
    String className = UPPER_CAMEL_FORMAT.convert(genConfig.getTableName());

    // 小写开头的类名
    String changeClassName = LOWER_CAMEL_FORMAT.convert(genConfig.getTableName());
    // 判断是否去除表前缀
    if (StringUtils.isNotEmpty(genConfig.getPrefix())) {
      className =
          UPPER_CAMEL_FORMAT.convert(genConfig.getTableName().replace(genConfig.getPrefix(), ""));
      changeClassName =
          LOWER_CAMEL_FORMAT.convert(genConfig.getTableName().replace(genConfig.getPrefix(), ""));
    }
    // 保存类名
    genMap.put("className", className);
    // 保存小写开头的类名
    genMap.put("changeClassName", changeClassName);
    // 存在 Timestamp 字段
    genMap.put("hasTimestamp", false);
    // 查询类中存在 Timestamp 字段
    genMap.put("queryHasTimestamp", false);
    // 存在 BigDecimal 字段
    genMap.put("hasBigDecimal", false);
    // 查询类中存在 BigDecimal 字段
    genMap.put("queryHasBigDecimal", false);
    // 是否需要创建查询
    genMap.put("hasQuery", false);
    // 自增主键
    genMap.put("auto", false);
    // 存在字典
    genMap.put("hasDict", false);
    // 存在日期注解
    genMap.put("hasDateAnnotation", false);
    // 保存字段信息
    List<Map<String, Object>> columns = new ArrayList<>();
    // 保存查询字段的信息
    List<Map<String, Object>> queryColumns = new ArrayList<>();
    // 存储字典信息
    List<String> dicts = new ArrayList<>();
    // 存储 between 信息
    List<Map<String, Object>> betweens = new ArrayList<>();
    // 存储不为空的字段信息
    List<Map<String, Object>> isNotNullColumns = new ArrayList<>();

    for (ColumnInfo column : columnInfos) {
      Map<String, Object> listMap = new HashMap<>(16);
      // 字段描述
      listMap.put("remark", column.getRemark());
      // 字段类型
      listMap.put("columnKey", column.getKeyType());
      // 主键类型
      String colType = new ConvertUtil().mysqlToJava(column.getColumnType());
      // 小写开头的字段名
      String changeColumnName = LOWER_CAMEL_FORMAT.convert(column.getColumnName());
      // 大写开头的字段名
      String capitalColumnName = UPPER_CAMEL_FORMAT.convert(column.getColumnName());
      if (PK.equals(column.getKeyType())) {
        // 存储主键类型
        genMap.put("pkColumnType", colType);
        // 存储小写开头的字段名
        genMap.put("pkChangeColName", changeColumnName);
        // 存储大写开头的字段名
        genMap.put("pkCapitalColName", capitalColumnName);
      }
      // 是否存在 Timestamp 类型的字段
      if (TIMESTAMP.equals(colType)) {
        genMap.put("hasTimestamp", true);
      }
      // 是否存在 BigDecimal 类型的字段
      if (BIGDECIMAL.equals(colType)) {
        genMap.put("hasBigDecimal", true);
      }
      // 主键是否自增
      if (EXTRA.equals(column.getExtra())) {
        genMap.put("auto", true);
      }
      // 主键存在字典
      if (StringUtils.isNotBlank(column.getDictName())) {
        genMap.put("hasDict", true);
        dicts.add(column.getDictName());
      }

      // 存储字段类型
      listMap.put("columnType", colType);
      // 存储字原始段名称
      listMap.put("columnName", column.getColumnName());
      // 不为空
      listMap.put("istNotNull", column.getNotNull());
      // 字段列表显示
      listMap.put("columnShow", column.getListShow());
      // 表单显示
      listMap.put("formShow", column.getFormShow());
      // 表单组件类型
      listMap.put(
          "formType",
          StringUtils.isNotBlank(column.getFormType()) ? column.getFormType() : "Input");
      // 小写开头的字段名称
      listMap.put("changeColumnName", changeColumnName);
      // 大写开头的字段名称
      listMap.put("capitalColumnName", capitalColumnName);
      // 字典名称
      listMap.put("dictName", column.getDictName());
      // 日期注解
      listMap.put("dateAnnotation", column.getDateAnnotation());
      if (StringUtils.isNotBlank(column.getDateAnnotation())) {
        genMap.put("hasDateAnnotation", true);
      }
      // 添加非空字段信息
      if (column.getNotNull()) {
        isNotNullColumns.add(listMap);
      }
      // 判断是否有查询，如有则把查询的字段set进columnQuery
      if (!StringUtils.isBlank(column.getQueryType())) {
        // 查询类型
        listMap.put("queryType", column.getQueryType());
        // 是否存在查询
        genMap.put("hasQuery", true);
        if (TIMESTAMP.equals(colType)) {
          // 查询中存储 Timestamp 类型
          genMap.put("queryHasTimestamp", true);
        }
        if (BIGDECIMAL.equals(colType)) {
          // 查询中存储 BigDecimal 类型
          genMap.put("queryHasBigDecimal", true);
        }
        if ("between".equalsIgnoreCase(column.getQueryType())) {
          betweens.add(listMap);
        } else {
          // 添加到查询列表中
          queryColumns.add(listMap);
        }
      }
      // 添加到字段列表中
      columns.add(listMap);
    }
    // 保存字段列表
    genMap.put("columns", columns);
    // 保存查询列表
    genMap.put("queryColumns", queryColumns);
    // 保存字段列表
    genMap.put("dicts", dicts);
    // 保存查询列表
    genMap.put("betweens", betweens);
    // 保存非空字段信息
    genMap.put("isNotNullColumns", isNotNullColumns);
    return genMap;
  }

  /** 定义后端文件路径以及名称 */
  private static String getAdminFilePath(
      String templateName, GenConfig genConfig, String className, String rootPath) {
    String projectPath = rootPath + File.separator + genConfig.getModuleName();
    String packagePath =
        projectPath
            + File.separator
            + "src"
            + File.separator
            + "main"
            + File.separator
            + "java"
            + File.separator;
    if (!ObjectUtils.isEmpty(genConfig.getPack())) {
      packagePath += genConfig.getPack().replace(".", File.separator) + File.separator;
    }

    if ("Entity".equals(templateName)) {
      return packagePath + "domain" + File.separator + className + ".java";
    }

    if ("Controller".equals(templateName)) {
      return packagePath + "rest" + File.separator + className + "Controller.java";
    }

    if ("Service".equals(templateName)) {
      return packagePath + "service" + File.separator + className + "Service.java";
    }

    if ("ServiceImpl".equals(templateName)) {
      return packagePath
          + "service"
          + File.separator
          + "impl"
          + File.separator
          + className
          + "ServiceImpl.java";
    }

    if ("Dto".equals(templateName)) {
      return packagePath
          + "service"
          + File.separator
          + "dto"
          + File.separator
          + className
          + "Dto.java";
    }

    if ("QueryCriteria".equals(templateName)) {
      return packagePath
          + "service"
          + File.separator
          + "dto"
          + File.separator
          + className
          + "QueryCriteria.java";
    }

    if ("Mapper".equals(templateName)) {
      return packagePath
          + "service"
          + File.separator
          + "dto"
          + File.separator
          + className
          + "Mapper.java";
    }

    if ("Repository".equals(templateName)) {
      return packagePath + "repository" + File.separator + className + "Repository.java";
    }

    return null;
  }

  /** 定义前端文件路径以及名称 */
  private static String getFrontFilePath(
      String templateName, String apiPath, String path, String apiName) {

    if ("api".equals(templateName)) {
      return apiPath + File.separator + apiName + ".js";
    }

    if ("index".equals(templateName)) {
      return path + File.separator + "index.vue";
    }

    return null;
  }

  private static void genFile(
      File file,
      Template template,
      Map<String, Object> map)
      throws Exception {

    template.render(map, file);
  }
}
